package com.apobates.forum.grief;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Commons {
    private final static Logger logger = LoggerFactory.getLogger(Commons.class);

    /**
     * 将指定的字符串转化成Integer
     *
     * @param value 待转化的字符串
     * @param defaultValue 转化失败时的默认值
     * @return
     */
    public static int stringToInteger(String value, int defaultValue) {
        return stringToNumberic(value, val->Integer.valueOf(val.trim()), ()->defaultValue);
    }

    /**
     *
     * @param <R>
     * @param value
     * @param mapper
     * @param supplier
     * @return
     */
    private static <R> R stringToNumberic(String value, Function<String, R> mapper, Supplier<R> supplier){
        try {
            return Optional.ofNullable(value).map(mapper).orElseGet(supplier);
        } catch (NumberFormatException e) {
            return supplier.get();
        }
    }

    /**
     * 将指定的字符串转化成Long
     *
     * @param value 待转化的字符串
     * @param defaultValue 转化失败时的默认值
     * @return
     */
    public static long stringToLong(String value, long defaultValue) {
        return stringToNumberic(value, val->Long.valueOf(val.trim()), ()->defaultValue);
    }

    /**
     * 实例化java Bean对象
     * @param tClass 要求对象具有无参的构造函数
     * @param <T>
     * @return
     */
    public static <T> Optional<T> getInstance(Class<T> tClass){
        try {
            T ins = tClass.getDeclaredConstructor().newInstance();
            return Optional.ofNullable(ins);
        }catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e){
            return Optional.empty();
        }
    }

    /**
     * 设置对象的属性值
     * @param obj 目标对象
     * @param attrName 属性名称
     * @param attrVal 属性值
     * @throws NoSuchFieldException
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static void setAttrValByReflectField(Object obj, String attrName, Object attrVal) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
        Field field = obj.getClass().getDeclaredField(attrName);
        Class superClass = obj.getClass().getSuperclass();
        while (field == null && superClass != null) {
            try {
                field = superClass.getDeclaredField(attrName);
            } catch (NoSuchFieldException e) {
                superClass = superClass.getSuperclass();
            }
        }
        if (field == null) {
            return;
        }
        field.setAccessible(true);
        field.set(obj, attrVal);
    }

    /**
     * Whats the difference between Introspector vs just reflection?
     * Introspection uses reflection to do its work, but works at a higher level than reflection.Reflection finds Java-level fields and methods; introspection finds JavaBeans-level properties and events.
     * @see ://stackoverflow.com/questions/6796187/java-introspection-object-to-map
     *
     * @param obj
     * @return
     * @throws java.beans.IntrospectionException
     * @throws java.lang.IllegalAccessException
     * @throws java.lang.reflect.InvocationTargetException
     */
    public static Map<String, Object> introspect(Object obj) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        Map<String, Object> result = new HashMap<>();
        BeanInfo info = Introspector.getBeanInfo(obj.getClass());
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            Method reader = pd.getReadMethod();
            if (reader != null) {
                result.put(pd.getName(), reader.invoke(obj));
            }
        }
        return result;
    }

    /**
     * 合并集合
     *
     * @param <K>
     * @param <V>
     * @param baseMap 以此集合为基准
     * @param extMap 在基准的集合上合并此集合
     * @param remappingFunction 合并时的运作函数
     * @return
     */
    public static <K,V> Map<K,V> mergeMap(Map<K,V> baseMap, Map<K,V> extMap, final BiFunction<? super V, ? super V, ? extends V> remappingFunction){
        if(null == extMap || extMap.isEmpty()){
            return baseMap;
        }
        final Map<K, V> data = new HashMap<>(baseMap);
        extMap.forEach((newKey, newVal)->{
            data.merge(newKey, newVal, remappingFunction);
        });
        return data;
    }

    /**
     * 执行Stream的收集,使用Collectors.toMap
     *
     * @param <T> 流的类型
     * @param <K> 收集后的集合Key类型
     * @param <V> 收集后的集合Key类型
     * @param src 源流
     * @param keyMaper key的映射函数,注要重复性
     * @param valueMapper value的映射函数
     * @return
     */
    public static <T,K,V> Map<K,V> collectMap(Stream<T> src, Function<T, K> keyMaper, Function<T, V> valueMapper){
        return src.collect(Collectors.toMap(keyMaper, valueMapper));
    }

    /**
     * md5 散列
     *
     * @param unEncryptString 未加密的字符串
     * @return 散列失败返回null;
     */
    public static String md5(String unEncryptString){
        Objects.requireNonNull(unEncryptString);
        try {
            // MessageDigest instance for MD5
            MessageDigest md = MessageDigest.getInstance("MD5");
            // Update MessageDigest with input text in bytes
            md.update(unEncryptString.getBytes(StandardCharsets.UTF_8));
            // Get the hashbytes
            byte[] hashBytes = md.digest();
            //Convert hash bytes to hex format
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        }catch (Exception e){
            return null;
        }
    }

    /**
     * SHA-256 散列
     *
     * @param unEncryptString 未加密的字符串
     * @return 散列失败返回null;
     */
    public static String sha256(String unEncryptString){
        Objects.requireNonNull(unEncryptString);
        try {
            // Static getInstance method is called with hashing SHA
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            // Update MessageDigest with input text in bytes
            md.update(unEncryptString.getBytes(StandardCharsets.UTF_8));
            // Get the hashbytes
            byte[] hashBytes = md.digest();
            // Convert byte array into signum representation
            BigInteger number = new BigInteger(1, hashBytes);
            // Convert message digest into hex value
            StringBuilder hexString = new StringBuilder(number.toString(16));
            // Pad with leading zeros
            while (hexString.length() < 32) {
                hexString.insert(0, '0');
            }
            return hexString.toString();
        }catch (Exception e){
            return null;
        }
    }
    /**
     * sha256 散列
     * 使用org.apache.commons.codec实现
     *
     * @param unEncryptString 未加密的字符串
     * @param salt 盐
     * @return
     */
    public static String sha256(String unEncryptString, String salt) {
        Objects.requireNonNull(unEncryptString);
        Objects.requireNonNull(salt);
        String data = salt + unEncryptString;
        return sha256(data);
    }
    /**
     * 将字符串参数进行BASE64编码
     * @param str 待编码的字符串
     * @param failValue 编码失败或字符串为null时的操作
     * @return
     */
    public static String base64Encode(String str, Supplier<String> failValue){
        Objects.requireNonNull(str);
        return encode64(str, failValue.get());
    }

    /**
     * 对字符串参数进行BASE64解码
     * @param str 待解码的字符串
     * @param failValue 解码失败或字符串为null时的操作
     * @return
     */
    public static String base64Decode(String str, Supplier<String> failValue){
        Objects.requireNonNull(str);
        return decode64(str, failValue.get());
    }

    /**
     * 使用BASE64编码Cookie中保存的值
     *
     * @param value 保存在Cookie中的值
     * @param defaultValue 若编码失败使用此值
     * @return
     */
    private static String encode64(String value, String defaultValue) {
        try{
            return Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8.name()));
        }catch(NullPointerException | UnsupportedEncodingException ex){
            return defaultValue;
        }
    }

    /**
     * 使用BASE64解码Cookie中保存的值
     *
     * @param value 保存在Cookie中的值
     * @param defaultValue 若解码失败使用此值
     * @return
     */
    private static String decode64(String value, String defaultValue) {
        try {
            byte[] dataBytes = Base64.getDecoder().decode(value);
            return new String(dataBytes, StandardCharsets.UTF_8.name());
        } catch (NullPointerException | UnsupportedEncodingException ex) {
            return defaultValue;
        }
    }

    public static void main(String[] args) {
        String input = "JavaInterviewPoint";
        System.out.println("MD5: "+md5(input)); //552a3ac767ac33154c7c5299a5ea8a02
        System.out.println("SHA256: "+sha256(input)); //523fe1efad36015755b70a0b635c4afa76478c1a3c01ef0e87903b4d43a2b212
    }
}
